1 /**
2 Copyright: Copyright (c) 2017, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 
10 Handles console logging in pretty colors.
11 
12 The module disables colors when stdout and stderr isn't a TTY that support
13 colors. This is to avoid ASCII escape sequences in piped output.
14 
15 Credit goes to the developers of Dub. A significant part of the color handling
16 is copied from that project.
17 */
18 module code_checker.logger;
19 
20 import std.algorithm : among;
21 import std.stdio : writeln, writefln, stderr, stdout;
22 import logger = std.experimental.logger;
23 import std.experimental.logger : LogLevel;
24 
25 import colorize : Color, Background, Mode;
26 
27 /// The verbosity level of the logging to use.
28 enum VerboseMode {
29     /// Warning+
30     minimal,
31     /// Info+
32     info,
33     /// Trace+
34     trace,
35     /// Warnings+
36     warning,
37 }
38 
39 void confLogger(VerboseMode mode) {
40     switch (mode) {
41     case VerboseMode.info:
42         logger.globalLogLevel = logger.LogLevel.info;
43         logger.sharedLog = new SimpleLogger(logger.LogLevel.info);
44         break;
45     case VerboseMode.trace:
46         logger.globalLogLevel = logger.LogLevel.all;
47         logger.sharedLog = new DebugLogger(logger.LogLevel.all);
48         logger.info("Debug mode activated");
49         break;
50     case VerboseMode.warning:
51         logger.globalLogLevel = logger.LogLevel.warning;
52         logger.sharedLog = new SimpleLogger(logger.LogLevel.info);
53         break;
54     default:
55         logger.globalLogLevel = logger.LogLevel.info;
56         logger.sharedLog = new SimpleLogger(logger.LogLevel.info);
57     }
58 }
59 
60 private:
61 
62 /**
63  * Whether to print text with colors or not, defaults to true but will be set
64  * to false in initLogging() if stdout or stderr are not a TTY (which means the
65  * output is probably being piped and we don't want ASCII escape chars in it)
66 */
67 shared bool _printColors = true;
68 shared bool _isColorsInitialized = false;
69 
70 // The width of the prefix.
71 immutable _prefixWidth = 8;
72 
73 /**
74  * It will detect whether or not stdout/stderr are a console/TTY and will
75  * consequently disable colored output if needed.
76  *
77  * Forgetting to call the function will result in ASCII escape sequences in the
78  * piped output, probably an undesiderable thing.
79  */
80 void initLogging() @trusted {
81     if (_isColorsInitialized)
82         return;
83     scope (exit)
84         _isColorsInitialized = true;
85 
86     // Initially enable colors, we'll disable them during this functions if we
87     // find any reason to
88     _printColors = true;
89 
90     version (Windows) {
91         _printColors = false;
92     } else {
93         import core.stdc.stdio;
94         import core.sys.posix.unistd;
95 
96         if (!isatty(STDERR_FILENO) || !isatty(STDOUT_FILENO))
97             _printColors = false;
98     }
99 }
100 
101 class SimpleLogger : logger.Logger {
102     this(const LogLevel lvl = LogLevel.warning) @safe {
103         super(lvl);
104         initLogging;
105     }
106 
107     override void writeLogMsg(ref LogEntry payload) @trusted {
108         auto out_ = stderr;
109         auto use_color = Color.red;
110         auto use_mode = Mode.bold;
111         const use_bg = Background.black;
112 
113         switch (payload.logLevel) {
114         case LogLevel.trace:
115             out_ = stdout;
116             use_color = Color.white;
117             use_mode = Mode.init;
118             break;
119         case LogLevel.info:
120             out_ = stdout;
121             use_color = Color.white;
122             break;
123         default:
124         }
125 
126         import std.conv : to;
127         import colorize;
128 
129         out_.writefln("%s: %s", payload.logLevel.to!string.color(use_color,
130                 use_bg, use_mode), payload.msg);
131     }
132 }
133 
134 class DebugLogger : logger.Logger {
135     this(const logger.LogLevel lvl = LogLevel.trace) {
136         super(lvl);
137         initLogging;
138     }
139 
140     override void writeLogMsg(ref LogEntry payload) @trusted {
141         auto out_ = stderr;
142         auto use_color = Color.red;
143         auto use_mode = Mode.bold;
144         const use_bg = Background.black;
145 
146         switch (payload.logLevel) {
147         case LogLevel.trace:
148             out_ = stdout;
149             use_color = Color.white;
150             use_mode = Mode.init;
151             break;
152         case LogLevel.info:
153             out_ = stdout;
154             use_color = Color.white;
155             break;
156         default:
157         }
158 
159         import std.conv : to;
160         import colorize;
161 
162         out_.writefln("%s: %s [%s:%d]", payload.logLevel.to!string.color(use_color,
163                 use_bg, use_mode), payload.msg, payload.funcName, payload.line);
164     }
165 }